package com.example.cm_utils;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import dalvik.system.BaseDexClassLoader;

public class LoadFileUtil {
    private static final String TAG = "LoadUtil";

    /**
     *
     * @param context   上下文
     * @param pluginDexFile   dex路径
     * @param insertBeforeHost  true:插入 oldDex 前面，false:插入 oldDex 后面
     */
    @SuppressLint("DiscouragedPrivateApi")
    public static void loadDexFile(Context context,String pluginDexFile,boolean insertBeforeHost) {
        Log.v(TAG,"============================================ loadDexFile start ============================================");
        File file = new File(pluginDexFile);
        if(file.exists()){
            Log.v(TAG,"pluginDexFile: "+file.getPath()+" exists >_<");
            file.setReadOnly();
        }else{
            Log.e(TAG,file.getPath()+" not exists!!!");
            Log.v(TAG,"============================================ loadDexFile end ============================================");
            return;
        }

        ClassLoader classLoader = context.getClassLoader();

        if(!(classLoader instanceof BaseDexClassLoader)){
            Log.e(TAG,"the classLoader not extends BaseDexClassLoader");
            Log.v(TAG,"============================================ loadDexFile end ============================================");
            return;
        }

        try {
            //1.获取BaseDexClassLoaderClass对象 并获取 DexPathList pathList 属性
            Class<BaseDexClassLoader> baseDexClassLoaderClass = BaseDexClassLoader.class;
             Field pathListField = baseDexClassLoaderClass.getDeclaredField("pathList");
            pathListField.setAccessible(true);

            //2.获取当前classLoader对象的pathList值
            Object hostPathList = pathListField.get(classLoader);

            //3.再获取 pathList 里面的 dexElements 属性值
            Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
            Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
            dexElementsField.setAccessible(true);

            Object[] hostDexElements = (Object[])dexElementsField.get(hostPathList);
            Log.v(TAG,"hostDexElements:"+ Arrays.toString(hostDexElements));

            //4.创建一个 pluginDexElements 用来存放外部的dex文件 调用 makeDexElements 方法
            Method makeDexElements = dexPathListClass.getDeclaredMethod("makeDexElements", List.class, File.class, List.class, ClassLoader.class);
            makeDexElements.setAccessible(true);
            List<File> files = new ArrayList<>();
            files.add(file);
            List<IOException> ioExceptions = new ArrayList<>();
            Object[] pluginDexElements = (Object[]) makeDexElements.invoke(null, files, context.getCacheDir().getAbsoluteFile(), ioExceptions, classLoader);
            Log.v(TAG,"pluginDexElements:"+ Arrays.toString(pluginDexElements));

            //5.将 pluginDexElements 和 hostDexElements 进行合并
            Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),
                    hostDexElements.length + pluginDexElements.length);

            if(insertBeforeHost){
                System.arraycopy(pluginDexElements, 0, newDexElements,
                        0, pluginDexElements.length);
                System.arraycopy(hostDexElements, 0, newDexElements,
                        pluginDexElements.length, hostDexElements.length);
            }else{
                System.arraycopy(hostDexElements, 0, newDexElements,
                        0, hostDexElements.length);
                System.arraycopy(pluginDexElements, 0, newDexElements,
                        hostDexElements.length, pluginDexElements.length);
            }

            //6.修改原有的值
            dexElementsField.set(hostPathList,newDexElements);
            Log.v(TAG,"newDexElements:"+ Arrays.toString(newDexElements));
            Log.v(TAG,"success");
        }catch (Exception e){
            Log.e(TAG,"e:"+e);
        }
        Log.v(TAG,"============================================ loadDexFile end ============================================");

    }

}
